// $Id: Column.cs 64 2008-05-06 18:29:47Z nvivo $

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Xml;
using System.Xml.XPath;

namespace DBLGen
{
	[System.Diagnostics.DebuggerDisplay("Column: {Name}")]
	[DefaultProperty("Alias")]
	public sealed class Column
	{
		private Table _table;

		private string _name;
		private string _sqlType;
		private int? _length;
		private int? _octetLength;
		private int? _precision;
		private int? _scale;
		private bool _isNullable;
		private bool _isComputed;
		private bool _isPrimaryKey;
		private bool _isForeignKey;

		private string _referencedSchema;
		private string _referencedTable;
		private string _referencedColumn;

		// Code data

		private string _alias;
		private string _type;
		private string _comments;
		private bool _isReadOnly;

		private bool _canExport = true;
		private CustomAttributes _customAttributes;


		#region Fields and Properties
		
		/// <summary>
		/// Gets the table that owns this column.
		/// </summary>
		[Browsable(false)]
		public Table Table
		{
			get
			{
				return _table;
			}
			internal set
			{
				_table = value;
			}
		}

		/// <summary>
		/// Gets or sets the name of the table.
		/// </summary>
		[Category("Database")]
		[Description("The name of the column.")]
		[TypeConverter(typeof(UI.Design.ColumnIdentifierTypeConverter))]
		public string Name
		{
			get
			{
				return _name ?? String.Empty;
			}
			set
			{
				_name = value;
			}
		}

		/// <summary>
		/// The type used in the database for this column.
		/// </summary>
		[Category("Database")]
		[Description("The sql type of the column.")]
		public string SqlType
		{
			get
			{
				return _sqlType ?? String.Empty;
			}
			set
			{
				_sqlType = value;
			}
		}

		/// <summary>
		/// The length of the column in characters.
		/// </summary>
		[Category("Database")]
		[Description("The length of the column in characters.")]
		public int? Length
		{
			get
			{
				return _length;
			}
			set
			{
				if (value.HasValue && value.Value < 0)
					_length = null;
				else
					_length = value;
			}
		}

		/// <summary>
		/// The length of the column in bytes.
		/// </summary>
		[Category("Database")]
		[Description("For character types, the length of the column in bytes.")]
		public int? OctetLength
		{
			get
			{
				return _octetLength;
			}
			set
			{
				if (value.HasValue && value.Value < 0)
					_octetLength = null;
				else
					_octetLength = value;
			}
		}

		/// <summary>
		/// The total number of digits or characters the type can hold.
		/// </summary>
		[Category("Database")]
		[Description("The numeric precision of the column.")]
		public int? Precision
		{
			get
			{
				return _precision;
			}
			set
			{
				if (value.HasValue && value.Value < 0)
					_precision = null;
				else
					_precision = value;
			}
		}

		/// <summary>
		/// The number of digits to the right of the decimal separator in a number.
		/// </summary>
		[Category("Database")]
		[Description("The numeric scale of the column.")]
		public int? Scale
		{
			get
			{
				return _scale;
			}
			set
			{
				if (value.HasValue && value.Value < 0)
					_scale = null;
				else
					_scale = value;
			}
		}

		/// <summary>
		/// Returns true if the value indicating if the value can be NULL, otherwise false.
		/// </summary>
		[Category("Database")]
		[Description("Defines if the column accepts NULL values.")]
		public bool IsNullable
		{
			get
			{
				return _isNullable;
			}
			set
			{
				_isNullable = value;
			}
		}

		/// <summary>
		/// Returns true if the column is computed.
		/// </summary>
		[Category("Database")]
		[Description("Defines if the column is a computed column.")]
		public bool IsComputed
		{
			get
			{
				return _isComputed;
			}
			set
			{
				_isComputed = value;
			}
		}

		/// <summary>
		/// Returns true if the column is used as a primary key, otherwise false.
		/// </summary>
		[Category("Database")]
		[Description("Defines if the column composes a primary key.")]
		public bool IsPrimaryKey
		{
			get
			{
				return _isPrimaryKey;
			}
			set
			{
				_isPrimaryKey = value;
			}
		}

		/// <summary>
		/// Returns true if the column is used as a foreign key, otherwise false.
		/// </summary>
		[Category("Database")]
		[Description("Defines if the column composes a foreign key.")]
		public bool IsForeignKey
		{
			get
			{
				return _isForeignKey;
			}
			set
			{
				_isForeignKey = value;
			}
		}


		/// <summary>
		/// Gets or sets an alias for the column to be used in the template.
		/// </summary>
		[Category("Code")]
		[Description("An alias to be used by the column in your code.")]
		public string Alias
		{
			get
			{
				return _alias ?? String.Empty;
			}
			set
			{
				_alias = value;
			}
		}

		/// <summary>
		/// Gets or sets the type of the column in the target language.
		/// </summary>
		[Category("Code")]
		[Description("The type of the data in the target language.")]
		public string Type
		{
			get
			{
				return _type ?? String.Empty;
			}
			set
			{
				_type = value;
			}
		}

		/// <summary>
		/// Gets or sets the comments about the column.
		/// </summary>
		[Category("Code")]
		public string Comments
		{
			get
			{
				return _comments ?? String.Empty;
			}
			set
			{
				_comments = value;
			}
		}

		/// <summary>
		/// Gets or sets a value indicating that the column should be read-only.
		/// </summary>
		[Category("Code")]
		public bool IsReadOnly
		{
			get
			{
				return _isReadOnly;
			}
			set
			{
				_isReadOnly = value;
			}
		}

		[Category("Code")]
		[Description("Defines if the column can be exported.")]
		public bool CanExport
		{
			get
			{
				return _canExport;
			}
			set
			{
				_canExport = value;
			}
		}

		/// <summary>
		/// Gets the custom attribute for the column.
		/// </summary>
		[Category("Code")]
		public CustomAttributes CustomAttributes
		{
			get
			{
				if (_customAttributes == null)
					_customAttributes = new CustomAttributes();

				return _customAttributes;
			}
			set
			{
				_customAttributes = value;
			}
		}

		[Category("References")]
		public string ReferencedSchema
		{
			get
			{
				return _referencedSchema ?? String.Empty;
			}
			set
			{
				_referencedSchema = value;
			}
		}

		[Category("References")]
		public string ReferencedTable
		{
			get
			{
				return _referencedTable ?? String.Empty;
			}
			set
			{
				_referencedTable = value;
			}
		}

		[Category("References")]
		public string ReferencedColumn
		{
			get
			{
				return _referencedColumn ?? String.Empty;
			}
			set
			{
				_referencedColumn = value;
			}
		}

		#endregion

		/// <summary>
		/// If the column is a foreign key, returns the table it references to.
		/// </summary>
		public Table GetReferencedTable()
		{
			if (IsForeignKey && Table != null && Table.Project != null)
			{
				return Table.Project.Tables.Find(_referencedSchema, _referencedTable);
			}
			else
			{
				return null;
			}
		}

		/// <summary>
		/// If the column is a foreign key, returns the column it references to.
		/// </summary>
		public Column GetReferencedColumn()
		{
			Table t = GetReferencedTable();

			if (t != null)
			{
				return t.Columns.Find(_referencedColumn);
			}
			else
			{
				return null;
			}

		}

		public void MergeFrom(Column column)
		{
			this.SqlType = column.SqlType;
			this.Length = column.Length;
			this.OctetLength = column.OctetLength;
			this.Precision = column.Precision;
			this.Scale = column.Scale;
			this.IsNullable = column.IsNullable;
			this.IsComputed = column.IsComputed;
			this.IsPrimaryKey = column.IsPrimaryKey;
			this.IsForeignKey = column.IsForeignKey;

			this._referencedSchema = column._referencedSchema;
			this._referencedTable = column._referencedTable;
			this._referencedColumn = column._referencedColumn;
		}

		#region Xml Persistence

		internal static Column LoadFromXml(XPathNavigator navigator)
		{
			string uri = navigator.NamespaceURI;

			string name = navigator.GetAttribute("name", uri);
			string sqlType = navigator.GetAttribute("sqlType", uri);
			string length = navigator.GetAttribute("length", uri);
			string octetLength = navigator.GetAttribute("octetLength", uri);
			string precision = navigator.GetAttribute("precision", uri);
			string scale = navigator.GetAttribute("scale", uri);
			bool isNullable = navigator.SelectSingleNode("@isNullable").ValueAsBoolean;
			bool isComputed = navigator.SelectSingleNode("@isComputed").ValueAsBoolean;
			bool isPrimaryKey = navigator.SelectSingleNode("@isPrimaryKey").ValueAsBoolean;
			bool isForeignKey = navigator.SelectSingleNode("@isForeignKey").ValueAsBoolean;

			string refSchema = navigator.GetAttribute("referencedSchema", uri);
			string refTable = navigator.GetAttribute("referencedTable", uri);
			string refColumn = navigator.GetAttribute("referencedColumn", uri);

			string alias = navigator.GetAttribute("alias", uri);
			string type = navigator.GetAttribute("type", uri);
			bool isReadOnly = navigator.SelectSingleNode("@isReadOnly").ValueAsBoolean;
			bool canExport = navigator.SelectSingleNode("@canExport").ValueAsBoolean;

			string comments = null;

			if (navigator.MoveToChild("comments", uri))
			{
				comments = navigator.Value;
				navigator.MoveToParent();
			}

			CustomAttributes ca = CustomAttributes.LoadXml(navigator);

			Column c = new Column();

			// database data

			c.Name = name;
			c.SqlType = sqlType;

			if (!String.IsNullOrEmpty(length))
				c.Length = Convert.ToInt32(length);

			if (!String.IsNullOrEmpty(octetLength))
				c.OctetLength = Convert.ToInt32(octetLength);

			if (!String.IsNullOrEmpty(precision))
				c.Precision = Convert.ToInt32(precision);

			if (!String.IsNullOrEmpty(scale))
				c.Scale = Convert.ToInt32(scale);

			c.IsNullable = isNullable;
			c.IsComputed = isComputed;
			c.IsPrimaryKey = isPrimaryKey;
			c.IsForeignKey = isForeignKey;

			c.ReferencedSchema = refSchema;
			c.ReferencedTable = refTable;
			c.ReferencedColumn = refColumn;

			// code data

			c.Alias = alias;
			c.Type = type;
			c.Comments = comments;
			c.IsReadOnly = isReadOnly;

			c.CanExport = canExport;
			c.CustomAttributes = ca;

			return c;
		}

		internal void SaveToXml(XmlWriter writer)
		{
			writer.WriteStartElement("column");

			// database data

			writer.WriteAttributeString("name", Name);

			if (!String.IsNullOrEmpty(SqlType))
				writer.WriteAttributeString("sqlType", SqlType);

			if (Length.HasValue)
				writer.WriteAttributeString("length", Length.Value.ToString());

			if (OctetLength.HasValue)
				writer.WriteAttributeString("octetLength", OctetLength.Value.ToString());

			if (Precision.HasValue)
				writer.WriteAttributeString("precision", Precision.Value.ToString());

			if (Scale.HasValue)
				writer.WriteAttributeString("scale", Scale.Value.ToString());

			writer.WriteAttributeString("isNullable", IsNullable ? "1" : "0");
			writer.WriteAttributeString("isComputed", IsComputed ? "1" : "0");
			writer.WriteAttributeString("isPrimaryKey", IsPrimaryKey ? "1" : "0");
			writer.WriteAttributeString("isForeignKey", IsForeignKey ? "1" : "0");

			if (!String.IsNullOrEmpty(_referencedSchema))
				writer.WriteAttributeString("referencedSchema", _referencedSchema);

			if (!String.IsNullOrEmpty(_referencedTable))
				writer.WriteAttributeString("referencedTable", _referencedTable);

			if (!String.IsNullOrEmpty(_referencedColumn))
				writer.WriteAttributeString("referencedColumn", _referencedColumn);

			// code data

			if (!String.IsNullOrEmpty(Alias))
				writer.WriteAttributeString("alias", Alias);

			if (!String.IsNullOrEmpty(Type))
				writer.WriteAttributeString("type", Type);

			writer.WriteAttributeString("isReadOnly", IsReadOnly ? "1" : "0");
			writer.WriteAttributeString("canExport", CanExport ? "1" : "0");

			if (!String.IsNullOrEmpty(Comments))
			{
				writer.WriteStartElement("comments");
				writer.WriteCData(Comments);
				writer.WriteEndElement();
			}

			// custom attributes
			CustomAttributes.SaveXml(writer);

			writer.WriteEndElement();
		}

		#endregion
	}
}
